package com.sukaiyi.giteejavaapi.http.nativeimpl;

import cn.hutool.core.io.IoUtil;
import com.sukaiyi.giteejavaapi.http.HttpEventCallback;
import com.sukaiyi.giteejavaapi.http.HttpService;
import com.sukaiyi.giteejavaapi.http.model.HttpRequest;
import com.sukaiyi.giteejavaapi.http.model.HttpResponse;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author sukaiyi
 * @date 2020/06/30
 */
public class HttpServiceNativeImpl implements HttpService {

    private final int availableProcessors = Runtime.getRuntime().availableProcessors();

    private final ThreadPoolExecutor httpRequestThreadPoolExecutor = new ThreadPoolExecutor(
            availableProcessors * 2,
            availableProcessors * 3,
            1,
            TimeUnit.MINUTES,
            new ArrayBlockingQueue<>(availableProcessors * 3)
    );

    @Override
    public void invoke(HttpRequest request, HttpEventCallback callback) {
        String url = request.getUrl();
        String method = request.getMethod();
        Map<String, String> headers = request.getHeaders();
        boolean doOutput = Arrays.asList("POST", "PUT", "PATCH").contains(method);
        String requestBody = request.getRequestBody();
        byte[] requestBodyBytes = Optional.ofNullable(requestBody)
                .map(e -> e.getBytes(StandardCharsets.UTF_8))
                .orElse(new byte[0]);

        httpRequestThreadPoolExecutor.submit(() -> {
            HttpURLConnection conn = null;
            OutputStream os = null;
            InputStream is = null;
            try {
                conn = (HttpURLConnection) new URL(url).openConnection();
                conn.setDoOutput(doOutput);

                if (Objects.equals("PATCH", method)) {
                    conn.setRequestProperty("X-HTTP-Method-Override", "PATCH");
                    conn.setRequestMethod("POST");
                } else {
                    conn.setRequestMethod(method);
                }

                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    conn.setRequestProperty(entry.getKey(), entry.getValue());
                }
                if (doOutput) {
                    conn.setRequestProperty("Content-Type", "application/json");
                    conn.setRequestProperty("Content-Length", String.valueOf(requestBodyBytes.length));
                }
                conn.connect();
                if (doOutput) {
                    os = conn.getOutputStream();
                    IoUtil.write(os, true, requestBodyBytes);
                }
                int responseCode = conn.getResponseCode();
                is = responseCode / 100 == 2 ? conn.getInputStream() : conn.getErrorStream();
                byte[] data = IoUtil.readBytes(is);
                Map<String, String> responseHeaders = new HashMap<>();
                for (Map.Entry<String, List<String>> entry : conn.getHeaderFields().entrySet()) {
                    responseHeaders.put(entry.getKey(), String.join(",", entry.getValue()));
                }
                HttpResponse response = new HttpResponse();
                response.setStatus(responseCode);
                response.setHeaders(responseHeaders);
                response.setData(data);
                callback.success(request, response);
            } catch (IOException e) {
                callback.failure(request, e);
            } finally {
                IoUtil.close(os);
                IoUtil.close(is);
            }
        });
    }

    @Override
    public CompletableFuture<HttpResponse> invoke(HttpRequest request) {
        CompletableFuture<HttpResponse> future = new CompletableFuture<>();
        invoke(request, new HttpEventCallback() {
            @Override
            public void failure(HttpRequest request, IOException e) {
                future.completeExceptionally(e);
            }

            @Override
            public void success(HttpRequest request, HttpResponse response) {
                future.complete(response);
            }
        });
        return future;
    }
}
